﻿namespace LinqEx.Tests
{
	using Microsoft.VisualStudio.TestTools.UnitTesting;
	using System;
	using System.Collections.Generic;
	using System.Linq;

	[TestClass]
	public class EnumerableExtensionTests
	{
		[TestMethod]
		public void Map()
		{
			int[] candidate = new int[] { 1, 2, 3, 4, 5, 6, 7 };

			int[] result = candidate.Map(e => 2 * e).ToArray();
			Assert.AreEqual(candidate.Length, result.Length);

			CollectionAssert.AreEqual(new int[] { 2, 4, 6, 8, 10, 12, 14 }, result);
		}

		[TestMethod]
		public void ReplaceByValue()
		{
			int[] candidate = new int[] { 1, 2, 3, 4, 3, 6, 7 };

			int[] result = candidate.Replace(3, 0).ToArray();
			Assert.AreEqual(candidate.Length, result.Length);

			CollectionAssert.AreEqual(new int[] { 1, 2, 0, 4, 0, 6, 7 }, result);
		}

		[TestMethod]
		public void ReplaceByPredicate()
		{
			int[] candidate = new int[] { 1, 2, 3, 4, 1, 6, 7 };
			
			int[] result = candidate.Replace(n => n < 3, 0).ToArray();
			Assert.AreEqual(candidate.Length, result.Length);

			CollectionAssert.AreEqual(new int[] { 0, 0, 3, 4, 0, 6, 7 }, result);
		}

		[TestMethod]
		public void ReplaceByMap()
		{
			int[] candidate = new int[] { 1, 2, 3, 4, 3, 6, 7 };
			IDictionary<int, int> map = new Dictionary<int, int>()
			{
				{ 2, 0 },
				{ 3, 9 }
			};

			int[] result = candidate.Replace(map).ToArray();
			Assert.AreEqual(candidate.Length, result.Length);

			CollectionAssert.AreEqual(new int[] { 1, 0, 9, 4, 9, 6, 7 }, result);
		}

		[TestMethod]
		public void Subsequence()
		{
			int[] candidate = new int[] { 1, 2, 3, 4, 5, 6, 7 };

			int[] result = candidate.Subsequence(2, 3).ToArray();

			CollectionAssert.AreEqual(new int[] { 3, 4, 5 }, result);
		}

		[TestMethod]
		public void Skip()
		{
			int[] candidate = new int[] { 1, 2, 3, 4, 5, 6, 7 };

			int[] result = candidate.Skip(2, 3).ToArray();

			CollectionAssert.AreEqual(new int[] { 1, 2, 6, 7 }, result);
		}

		[TestMethod]
		public void ExceptIndexes()
		{
			int[] candidate = new int[] { 1, 2, 3, 4, 5, 6, 7 };

			int[] result = candidate.Except(new int[] { 0, 3, 4 }).ToArray();

			CollectionAssert.AreEqual(new int[] { 2, 3, 6, 7 }, result);
		}

		[TestMethod]
		public void Except()
		{
			int[] candidate = new int[] { 1, 2, 3, 4, 5, 6, 7 };
			Func<int, bool> predicate = n => n < 3;

			Assert.IsTrue(candidate.Any(predicate));

			int[] result = candidate.Except(predicate).ToArray();

			Assert.IsTrue(!result.Any(predicate));
		}

		[TestMethod]
		public void CycleForward()
		{
			int[] candidate = new int[] { 1, 2, 3, 4, 5, 6, 7 };

			int[] result = candidate.CycleForward(0).ToArray();
			Assert.AreEqual(candidate.Length, result.Length);
			CollectionAssert.AreEqual(candidate, result);

			result = candidate.CycleForward(3).ToArray();
			Assert.AreEqual(candidate.Length, result.Length);
			CollectionAssert.AreEqual(new int[] { 5, 6, 7, 1, 2, 3, 4 }, result);
		}

		[TestMethod]
		public void CycleBackward()
		{
			int[] candidate = new int[] { 1, 2, 3, 4, 5, 6, 7 };

			int[] result = candidate.CycleBackward(0).ToArray();
			Assert.AreEqual(candidate.Length, result.Length);
			CollectionAssert.AreEqual(candidate, result);

			result = candidate.CycleBackward(3).ToArray();
			Assert.AreEqual(candidate.Length, result.Length);
			CollectionAssert.AreEqual(new int[] { 4, 5, 6, 7, 1, 2, 3 }, result);
		}

		[TestMethod]
		public void TakeIndexes()
		{
			int[] candidate = new int[] { 1, 2, 3, 4, 5, 6, 7 };

			int[] result = candidate.Take(new int[] { 0, 3, 4 }).ToArray();

			CollectionAssert.AreEqual(new int[] { 1, 4, 5 }, result);
		}

		[TestMethod]
		public void TakeEvery()
		{
			int[] candidate = new int[] { 1, 2, 3, 4, 5, 6, 7 };

			int[] result = candidate.TakeEvery(3).ToArray();

			CollectionAssert.AreEqual(new int[] { 3, 6 }, result);
		}

		[TestMethod]
		public void SkipEvery()
		{
			int[] candidate = new int[] { 1, 2, 3, 4, 5, 6, 7 };

			int[] result = candidate.SkipEvery(3).ToArray();

			CollectionAssert.AreEqual(new int[] { 1, 2, 4, 5, 7 }, result);
		}

		[TestMethod]
		public void SplitToFixedSizeParts()
		{
			int[] candidate = new int[] { 1, 2, 3, 4, 5, 6, 7 };

			int[][] result = candidate.Split(3).Select(e => e.ToArray()).ToArray();
			Assert.AreEqual(3, result.Length);

			Assert.AreEqual(3, result[0].Length);
			Assert.AreEqual(3, result[1].Length);
			Assert.AreEqual(1, result[2].Length);

			Assert.AreEqual(1, result[0][0]);
			Assert.AreEqual(2, result[0][1]);
			Assert.AreEqual(3, result[0][2]);
			Assert.AreEqual(4, result[1][0]);
			Assert.AreEqual(5, result[1][1]);
			Assert.AreEqual(6, result[1][2]);
			Assert.AreEqual(7, result[2][0]);
		}

		[TestMethod]
		public void SplitBySeparator()
		{
			int[] candidate = new int[] { 1, 2, 3, 4, 2, 6, 7 };

			int[][] result = candidate.Split(separator: 2).Select(e => e.ToArray()).ToArray();
			Assert.AreEqual(3, result.Length);

			Assert.AreEqual(1, result[0].Length);
			Assert.AreEqual(2, result[1].Length);
			Assert.AreEqual(2, result[2].Length);

			Assert.AreEqual(1, result[0][0]);
			Assert.AreEqual(3, result[1][0]);
			Assert.AreEqual(4, result[1][1]);
			Assert.AreEqual(6, result[2][0]);
			Assert.AreEqual(7, result[2][1]);
		}

		[TestMethod]
		public void Separate()
		{
			int[] candidate = new int[] { 1, 2, 3 };

			int[] result = candidate.Separate(0).ToArray();
			Assert.AreEqual(candidate.Length + (candidate.Length - 1), result.Length);

			CollectionAssert.AreEqual(new int[] { 1, 0, 2, 0, 3 }, result);
		}
        
        [TestMethod]
        public void PrependValue()
        {
            int[] candidate = new int[] { 1, 2, 3 };

            int[] result = candidate.Prepend(0).ToArray();

            CollectionAssert.AreEqual(new int[] { 0, 1, 2, 3 }, result);
        }

        [TestMethod]
        public void PrependSequence()
        {
            int[] candidate = new int[] { 1, 2, 3 };
            int[] second = new int[] { -2, -1, 0 };

            int[] result = candidate.Prepend(second).ToArray();

            CollectionAssert.AreEqual(new int[] { -2, -1, 0, 1, 2, 3 }, result);
        }

        [TestMethod]
        public void AppendValue()
        {
            int[] candidate = new int[] { 1, 2, 3 };

            int[] result = candidate.Append(4).ToArray();

            CollectionAssert.AreEqual(new int[] { 1, 2, 3, 4 }, result);
        }

        [TestMethod]
        public void AppendSequence()
        {
            int[] candidate = new int[] { 1, 2, 3 };
            int[] second = new int[] { 4, 5, 6 };

            int[] result = candidate.Append(second).ToArray();

            CollectionAssert.AreEqual(new int[] { 1, 2, 3, 4, 5, 6 }, result);
        }

		[TestMethod]
		public void Fold()
		{
			int[] candidate = new int[] { 1, 3, 2, 6, 0 };

			int result = candidate.Fold((x, y) => x + y);

			Assert.AreEqual(12, result);
		}

		[TestMethod]
		public void FoldWithSeed()
		{
			int[] candidate = new int[] { 1, 3, 2, 6, 0 };

			string result = candidate.Fold("Result: ", (x, y) => x + y.ToString());

			Assert.AreEqual("Result: 13260", result);
		}

		[TestMethod]
		public void FoldEntireSequence()
		{
			int[] candidate = new int[] { 1, 3, 2, 6, 0 };

			int result = candidate.Fold(Enumerable.Sum);

			Assert.AreEqual(12, result);
		}

		[TestMethod]
		public void BinaryFold()
		{
			int[] candidate = new int[] { 1, 3, 2, 6, 0, 7 };

			int[] result = candidate.BinaryFold((x, y) => x + y).ToArray();

			CollectionAssert.AreEqual(new int[] { 4, 8, 7 }, result);
		}

		[TestMethod]
		public void BinaryFoldRecursive()
		{
			int[] candidate = new int[] { 1, 3, 2, 6, 0, 7, -1, 2 };

			int result = candidate.BinaryFoldRecursive((x, y) => x + y);

			Assert.AreEqual(20, result);
		}

		[TestMethod]
		public void Differentiate()
		{
			int[] candidate = new int[] { 1, 3, 2, 6, 0 };

			int[] result = candidate.Differentiate((x, y) => y - x).ToArray();
			Assert.AreEqual(result.Length, candidate.Length - 1);

			CollectionAssert.AreEqual(new int[] { 2, -1, 4, -6 }, result);
		}

		[TestMethod]
		public void Unfold()
		{
			int[] candidate = new int[] { 1 };

			int[] result = candidate.Unfold(s => s.Last() + 1).Take(3).ToArray();

			CollectionAssert.AreEqual(new int[] { 1, 2, 3 }, result);
		}

		[TestMethod]
		public void IndexOf()
		{
			int[] candidate = new int[] { 1, 3, 2, 6, 0, 7, -1, 2 };

			int result = candidate.IndexOf(6);
			Assert.AreEqual(3, result);

			result = candidate.IndexOf(10);
			Assert.AreEqual(-1, result);
		}

		[TestMethod]
		public void IndexOfAny()
		{
			int[] candidate = new int[] { 1, 3, 2, 6, 0, 7, -1, 2 };

			int result = candidate.IndexOfAny(new int[] { 2, 6 });
			Assert.AreEqual(2, result);

			result = candidate.IndexOfAny(new int[] { 9, -4 });
			Assert.AreEqual(-1, result);
		}

		[TestMethod]
		public void IsSubsetOf()
		{
			int[] candidate = new int[] { 1, 2, 3, 4, 5, 6, 7 };

			int[] second = new int[] { 2, 1, 5 };
			Assert.IsTrue(second.IsSubsetOf(candidate));

			int[] secondStrict = new int[] { 1, 2, 3, 4, 5, 6, 7 };
			Assert.IsFalse(candidate.IsSubsetOf(secondStrict, strict: true));
		}

		[TestMethod]
		public void IsSupersetOf()
		{
			int[] candidate = new int[] { 1, 2, 3, 4, 5, 6, 7 };

			int[] second = new int[] { 2, 1, 5 };
			Assert.IsTrue(candidate.IsSupersetOf(second));

			int[] secondStrict = new int[] { 1, 2, 3, 4, 5, 6, 7 };
			Assert.IsFalse(candidate.IsSupersetOf(secondStrict, strict: true));
		}

		[TestMethod]
		public void SetEqual()
		{
			int[] candidate = new int[] { 1, 2, 3, 4, 5 };

			int[] second = new int[] { 2, 1, 5, 4, 3 };
			
			Assert.IsTrue(candidate.SetEqual(second));
		}

		[TestMethod]
		public void StartsWith()
		{
			int[] candidate = new int[] { 1, 2, 3, 4, 5, 6, 7 };

			int[] second = new int[] { 1, 2, 3 };

			Assert.IsTrue(candidate.StartsWith(second));
		}

		[TestMethod]
		public void EndsWith()
		{
			int[] candidate = new int[] { 1, 2, 3, 4, 5, 6, 7 };

			int[] second = new int[] { 5, 6, 7 };

			Assert.IsTrue(candidate.EndsWith(second));
		}

		[TestMethod]
		public void Apply()
		{
			int[] candidate = new int[] { 1, 2, 3, 4, 5, 6, 7 };

			int[] result = candidate.Apply(s => s.Select(e => 2 * e)).ToArray();

			CollectionAssert.AreEqual(new int[] { 2, 4, 6, 8, 10, 12, 14 }, result);
		}

		[TestMethod]
		public void MapReduce()
		{
			// Test Case: find the most common divisor

			int[] candidate = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };

			int result = candidate.MapReduce(Helpers.FindRealDivisors, d => d, (d, es) => new KeyValuePair<int, int>(d, es.Count())).OrderByDescending(kv => kv.Value).First().Key;

			Assert.AreEqual(2, result); 
		}

		[TestMethod]
		public void ForEach()
		{
			int[] candidate = new int[] { 1, 2, 3 };
			ICollection<int> result = new List<int>();

			candidate.ForEach(e => result.Add(e));

			CollectionAssert.AreEqual(candidate, result.ToArray());
		}

		[TestMethod]
		public void ForEachWithUniqueActions()
		{
			int[] candidate = new int[] { 1, 2, 3 };
			IEnumerable<Action<int>> actions = new [] {
				new Action<int>(e => Assert.AreEqual(e, 1)),
				new Action<int>(e => Assert.AreEqual(e, 2)),
				new Action<int>(e => Assert.AreEqual(e, 3))
			};

			candidate.ForEach(actions);
		}

		[TestMethod]
		public void ElementAtMax()
		{
			string candidate = "Hello world!";

			char result = candidate.GroupBy(c => c).ElementAtMax(g => g.Count()).Key;

			Assert.AreEqual('l', result);
		}

		[TestMethod]
		public void ElementAtMin()
		{
			string candidate = "aabccc";

			char result = candidate.GroupBy(c => c).ElementAtMin(g => g.Count()).Key;

			Assert.AreEqual('b', result);
		}

		[TestMethod]
		public void Combine()
		{
			int[] candidate = new int[] { 1, 2, 3 };
			int[] second = new int[] { 4, 5, 6 };

			int[] result = candidate.Combine(second, (x, y) => x + y).ToArray();
			Assert.AreEqual(result.Length, Math.Min(candidate.Length, second.Length));

			CollectionAssert.AreEqual(new int[] { 5, 7, 9 }, result);
		}


		private static class Helpers
		{
			public static IEnumerable<int> FindRealDivisors(int n)
			{
				for (int i = 2; i < n; i++)
					if (n % i == 0)
						yield return i;
			}
		}
	}
}
